// (C) Copyright 2012 Kystar. All rights reserved.

`timescale 1ns/100ps
`default_nettype none

module h_upscaler_core
(
    input  wire          I_sclk,
    input  wire          I_rst_n,
    input  wire          I_new_frame,
    input  wire          I_fifo_empty,
    input  wire [ 23: 0] I_fifo_q,
    input  wire [ 11: 0] I_fifo_usedw,
    output wire          O_fifo_rdreq,
    output reg  [ 23: 0] O_data_out,
    output reg           O_data_out_valid,
    output wire          O_upscale_even_line,
    input  wire [ 11: 0] I_reg_vin_width,
    input  wire [ 11: 0] I_reg_upscale_width
);

/******************************************************************************
                                <localparams>
******************************************************************************/
localparam
    POS_PRECISION = 15,
    MULT_PRECISION = 6;

localparam // state
    IDLE = 0,
    WAIT_FIFO = 1,
    SCALING_FIRST_RDREQ = 1<<1,
    SCALING_LINE = 1<<2,
    NEXT_LINE = 1<<3;

/******************************************************************************
                              <internal signals>
******************************************************************************/
reg  [ 11: 0] reg_vin_width;
reg  [ 11: 0] reg_upscale_width;
reg  [ 11: 0] reg_vin_width_sub_one;
reg  [ 11: 0] reg_upscale_width_sub_one;
reg  [ 12 + POS_PRECISION - 1: 0] x_pos;
reg  [ 12 + POS_PRECISION - 1: 0] x_pos_dly1;
reg  [ 12 + POS_PRECISION - 1: 0] x_pos_dly2;
reg  [ 12 + POS_PRECISION - 1: 0] x_pos_dly3;
reg  [ 12 + POS_PRECISION - 1: 0] last_x_pos;
reg  [ 3: 0] state;
reg  [ 3: 0] next_state;
reg  [ 7: 0] wait_cnt;
reg  [ 11: 0] fifo_rdreq_cnt;
reg  fifo_valid;
reg  [ 23: 0] data0;
reg  [ 23: 0] data1;
wire [ 7: 0] result_r;
wire [ 7: 0] result_g;
wire [ 7: 0] result_b;
reg  [ 11: 0] data_out_valid_cnt;
reg  scale_line_finish;
wire result_valid;
reg  divide_start;
wire [ 12 + POS_PRECISION - 1: 0] divide_result;
wire divide_finish;
reg  [ POS_PRECISION - 1: 0] reg_upscale_step;
reg  new_frame_dly;
reg  no_scale;
reg  no_scale_fifo_valid;
reg  data_in_valid;
reg  data_in_valid_dly1;
reg  data_in_valid_dly2;
reg  fifo_rdreq;
reg  [ 11: 0] data_in_valid_cnt;
reg  [ 10: 0] no_scale_fifo_valid_cnt;
reg  [ 7: 0] upscale_even_line_dly;
reg  upscale_even_line;

/******************************************************************************
                                <module body>
******************************************************************************/
assign O_fifo_rdreq = no_scale ? (!I_fifo_empty) : fifo_rdreq;

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        no_scale_fifo_valid <= 1'b0;
    else
        no_scale_fifo_valid <= O_fifo_rdreq;

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        no_scale_fifo_valid_cnt <= 'd0;
    else if (I_new_frame)
        no_scale_fifo_valid_cnt <= 'd0;
    else if (no_scale_fifo_valid_cnt == reg_vin_width)
        no_scale_fifo_valid_cnt <= 'd0;
    else if (no_scale_fifo_valid)
        no_scale_fifo_valid_cnt <= no_scale_fifo_valid_cnt + 1'b1;

always @(posedge I_sclk)
    if (I_new_frame)
        begin
        reg_vin_width <= I_reg_vin_width;
        reg_upscale_width <= I_reg_upscale_width;
        reg_vin_width_sub_one <= I_reg_vin_width - 1'b1;
        reg_upscale_width_sub_one <= I_reg_upscale_width - 1'b1;
        no_scale <= I_reg_vin_width == I_reg_upscale_width;
        end

always @(posedge I_sclk)
    begin
    new_frame_dly <= I_new_frame;
    divide_start <= new_frame_dly;
    end

serial_divider
#(
    .INT_BW(12),
    .FRAC_BW(POS_PRECISION)
)
u_serial_divider
(
    .I_sclk(I_sclk),
    .I_rst_n(I_rst_n),
    .I_numer(reg_vin_width_sub_one),
    .I_denum(reg_upscale_width_sub_one),
    .I_divide_start(divide_start),
    .O_result(divide_result),
    .O_divide_finish(divide_finish)
);

always @(posedge I_sclk)
    if (divide_finish)
        reg_upscale_step <= divide_result[POS_PRECISION-1:0];

//--------------------------------------------------------------------
// state machine : state
//--------------------------------------------------------------------
always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        state <= IDLE;
    else if (I_new_frame)
        state <= IDLE;
    else
        state <= next_state;

always @(*)
    case (state)
        IDLE:
            if (wait_cnt == 255 && !no_scale)
                next_state = WAIT_FIFO;
            else
                next_state = IDLE;
        WAIT_FIFO:
            if (I_fifo_usedw >= reg_vin_width)
                next_state = SCALING_FIRST_RDREQ;
            else
                next_state = WAIT_FIFO;
        SCALING_FIRST_RDREQ:
            next_state = SCALING_LINE;
        SCALING_LINE:
            if (scale_line_finish)
                next_state = NEXT_LINE;
            else
                next_state = SCALING_LINE;
        NEXT_LINE:
            next_state = WAIT_FIFO;
        default:
            next_state = IDLE;
    endcase

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        wait_cnt <= 'd0;
    else if (state == IDLE)
        wait_cnt <= wait_cnt + 1'b1;
    else
        wait_cnt <= 'd0;

//--------------------------------------------------------------------
// scaling
//--------------------------------------------------------------------
always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        x_pos <= 'd0;
    else if (state == SCALING_LINE)
        x_pos <= x_pos + reg_upscale_step;
    else
        x_pos <= 'd0;

always @(posedge I_sclk)
    begin
    x_pos_dly1 <= x_pos;
    x_pos_dly2 <= x_pos_dly1;
    x_pos_dly3 <= x_pos_dly2;
    end

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        last_x_pos <= 'd0;
    else if (state == SCALING_LINE)
        last_x_pos <= x_pos;
    else
        last_x_pos <= {(12 + POS_PRECISION){1'b1}};

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        fifo_rdreq <= 1'b0;
    else if (state == SCALING_FIRST_RDREQ)
        fifo_rdreq <= 1'b1;
    else if (state != SCALING_LINE)
        fifo_rdreq <= 1'b0;
    else if (fifo_rdreq_cnt == reg_vin_width)
        fifo_rdreq <= 1'b0;
    else if (fifo_rdreq_cnt == reg_vin_width - 1'b1 && fifo_rdreq)
        fifo_rdreq <= 1'b0;
    else if ((x_pos >> POS_PRECISION) != (last_x_pos >> POS_PRECISION))
        fifo_rdreq <= 1'b1;
    else
        fifo_rdreq <= 1'b0;

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        fifo_rdreq_cnt <= 'd0;
    else if (state != SCALING_LINE && state != SCALING_FIRST_RDREQ)
        fifo_rdreq_cnt <= 'd0;
    else if (fifo_rdreq)
        fifo_rdreq_cnt <= fifo_rdreq_cnt + 1'b1;

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        fifo_valid <= 1'b0;
    else
        fifo_valid <= fifo_rdreq;

always @(posedge I_sclk)
    if (fifo_valid)
        begin
        data0 <= data1;
        data1 <= I_fifo_q;
        end

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        data_in_valid <= 1'b0;
    else if (data_in_valid_cnt >= reg_upscale_width)
        data_in_valid <= 'd0;
    else
        data_in_valid <= state == SCALING_LINE;

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        data_in_valid_cnt <= 'd0;
    else if (state != SCALING_LINE)
        data_in_valid_cnt <= 'd0;
    else if (data_in_valid)
        data_in_valid_cnt <= data_in_valid_cnt + 1'b1;

always @(posedge I_sclk)
    begin
    data_in_valid_dly1 <= data_in_valid;
    data_in_valid_dly2 <= data_in_valid_dly1;
    end

dsp48_blending_core
#(
    .DATA_BW(8),
    .ALPHA_BW(POS_PRECISION),
    .MULT_PRECISION(MULT_PRECISION)
)
u_dsp48_blending_core_r
(
    .I_sclk(I_sclk),
    .I_clr(1'b0),
    .I_a(data1[23:16]),
    .I_b(data0[23:16]),
    .I_alpha(x_pos_dly3[POS_PRECISION-1:0]),
    .I_valid(data_in_valid_dly2),
    .O_result(result_r),
    .O_valid(result_valid)
);

dsp48_blending_core
#(
    .DATA_BW(8),
    .ALPHA_BW(POS_PRECISION),
    .MULT_PRECISION(MULT_PRECISION)
)
u_dsp48_blending_core_g
(
    .I_sclk(I_sclk),
    .I_clr(1'b0),
    .I_a(data1[15:8]),
    .I_b(data0[15:8]),
    .I_alpha(x_pos_dly3[POS_PRECISION-1:0]),
    .I_valid(),
    .O_result(result_g),
    .O_valid()
);

dsp48_blending_core
#(
    .DATA_BW(8),
    .ALPHA_BW(POS_PRECISION),
    .MULT_PRECISION(MULT_PRECISION)
)
u_dsp48_blending_core_b
(
    .I_sclk(I_sclk),
    .I_clr(1'b0),
    .I_a(data1[7:0]),
    .I_b(data0[7:0]),
    .I_alpha(x_pos_dly3[POS_PRECISION-1:0]),
    .I_valid(),
    .O_result(result_b),
    .O_valid()
);

always @(posedge I_sclk)
    if (no_scale)
        O_data_out <= I_fifo_q;
    else
        O_data_out <= {result_r,result_g,result_b};

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        O_data_out_valid <= 1'b0;
    else if (no_scale)
        O_data_out_valid <= no_scale_fifo_valid;        
    else if (state != SCALING_LINE)
        O_data_out_valid <= 1'b0;
    else if (data_out_valid_cnt == reg_upscale_width)
        O_data_out_valid <= 1'b0;
    else if (data_out_valid_cnt == reg_upscale_width - 1'b1 && O_data_out_valid)
        O_data_out_valid <= 1'b0;
    else
        O_data_out_valid <= result_valid;

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        data_out_valid_cnt <= 'd0;
    else if (state != SCALING_LINE)
        data_out_valid_cnt <= 'd0;
    else if (O_data_out_valid)
        data_out_valid_cnt <= data_out_valid_cnt + 1'b1;

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        scale_line_finish <= 1'b0;
    else if (state == SCALING_LINE
        && data_out_valid_cnt == reg_upscale_width)
        scale_line_finish <= 1'b1;
    else
        scale_line_finish <= 1'b0;

//--------------------------------------------------------------------
// O_upscale_even_line
//--------------------------------------------------------------------
always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        upscale_even_line <= 1'b0;
    else if (I_new_frame)
        upscale_even_line <= 1'b0;
    else if (no_scale)
        begin
        if (no_scale_fifo_valid_cnt == reg_vin_width)
            upscale_even_line <= ~upscale_even_line;
        end
    else if (state == NEXT_LINE)
        upscale_even_line <= ~upscale_even_line;

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        upscale_even_line_dly <= 'd0;
    else
        upscale_even_line_dly <= {upscale_even_line_dly[6:0],upscale_even_line};

assign O_upscale_even_line = upscale_even_line_dly[7];

endmodule

`default_nettype wire
